package com.mijie.homi.service.topic;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mijie.core.ApplicationProperties;
import com.mijie.exception.RecordNotFoundException;
import com.mijie.homi.common.constant.MCKey;
import com.mijie.homi.common.domain.SearchResult;
import com.mijie.homi.common.domain.timeline.TimelineEvent;
import com.mijie.homi.common.domain.topic.RelatedTopic;
import com.mijie.homi.common.domain.topic.Topic;
import com.mijie.homi.common.domain.topic.TopicType;
import com.mijie.homi.common.domain.user.User;
import com.mijie.homi.dao.topic.RelatedTopicDao;
import com.mijie.homi.dao.topic.TopicDao;
import com.mijie.homi.dao.topic.TopicOpinionDao;
import com.mijie.homi.dao.topic.TopicOptionsDao;
import com.mijie.homi.domain.commom.Review;
import com.mijie.homi.domain.commom.Review.Module;
import com.mijie.homi.domain.topic.ConsensusUser;
import com.mijie.homi.domain.topic.TopicOpinion;
import com.mijie.homi.domain.topic.TopicOptions;
import com.mijie.homi.service.cache.MemcacheService;
import com.mijie.homi.service.common.AbstractEventService;
import com.mijie.homi.service.notify.NotifyUserAgent;
import com.mijie.homi.service.timeline.TimelineService;
import com.mijie.homi.service.user.UserExtInfoService;
import com.mijie.homi.util.ServerHelper;
import com.mijie.util.HttpUtil;

@Service
public class TopicService extends AbstractEventService {

	@Autowired
	private TopicDao topicDao;
	@Autowired
	private TopicOptionsDao optionsDao;
	@Autowired
	private TopicOpinionDao opinionDao;
	@Autowired
	private RelatedTopicDao relatedTopidDao;
	@Autowired
	private MemcacheService mcs;
	@Autowired
	private UserExtInfoService userExtInfoService;
	@Autowired
	private NotifyUserAgent notifyUserAgent;
	@Autowired
	private TimelineService timelineService;
	
	
	private Logger logger = Logger.getLogger(TopicService.class);
	
	private String getTopicConsensusAmountKey(Integer gender,int optionId){
		if(gender==null){
			return "topicConsensusAmount_optId:"+optionId;
		}
		return "topicConsensusAmount_g:"+gender+"_optId:"+optionId;
	}
	
	@Override
	public Module getModule() {
		return Review.Module.TOPIC_OPINION;
	}
	

	@Transactional(rollbackFor={Exception.class})
	public List<TopicOptions> postTopic(Topic topic,String[] options,String choice) throws Exception{
		int id =  topicDao.postTopic(topic);
		if(id>0){
			List<TopicOptions> optionsList = new ArrayList<TopicOptions>();
			TopicOptions op =null;
			for(int i=0;i<options.length;i++){
				if(options[i].trim().isEmpty()) continue;
				op = new TopicOptions();
				op.setTopicId(topic.getId());
				op.setOptions(options[i]);
				optionsList.add(op);
			}
			int rows = (optionsList.size() == 0) ? 0 :settingOptions(optionsList);
			if(rows>0){
				//重新获取一次选项是为了让每个选项都有ID
				optionsList  = getOptions(topic.getId());
				if(!choice.trim().isEmpty()){
					//老版本一定要选择一个答案，新版本去掉该限制
					for(TopicOptions o : optionsList){
						if(o.getOptions().equals(choice)){
							int choiceSucc = choiceOption(topic.getAuthor(), topic.getId(), o.getId(), true);
							if(choiceSucc==0) throw new Exception("choiceOption fail during post new topic");
							break;
						}
					}
				}
			}else{
				throw new Exception("Post topic setting options fail.");
			}
			
			TimelineEvent event = new TimelineEvent();
			event.setEventId(topic.getId());
			event.setEventType(TimelineEvent.TYPE_TOPIC);
			event.setUserId(topic.getAuthor());
			rows = timelineService.addTimelineEvent(event);
			if(rows==0) throw new Exception("Post topic addTimelineEvent fail.");
			rows = topicDao.setTimelineIdForTopic(topic.getId(), event.getId());
			if(rows==0) throw new Exception("Post topic setTimelineIdForTopic fail.");
			topic.setTimelineId(event.getId());
			return optionsList;
		}
		return null;
	}
	
	@Transactional(rollbackFor={Exception.class})
	public int setTimelineIdForTopic(int id,int timelineId){
		return topicDao.setTimelineIdForTopic(id, timelineId);
	}
	
	public Map<Integer,Topic> listTopicMapByIds(List<Integer> list){
		return topicDao.listTopicMapByIds(list);
	}
	
	public int anonymousTopic(int author,int id){
		int succ = topicDao.anonymousTopic(author, id);
		if(succ>0){
			mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(id));
		}
		return succ;
	}
	
	public Integer getTopicIdByOptionId(int optionId){
		Integer topicId = (Integer) mcs.get(MCKey.TOPICID_BY_OPTIONID, String.valueOf(optionId));
		if(topicId==null){
			topicId = optionsDao.getTopicIdByOptionId(optionId);
			if(topicId!=null && topicId!=0)
				mcs.set(MCKey.TOPICID_BY_OPTIONID, String.valueOf(optionId), MCKey.TOPIC_CACHE_TIMEOUT, topicId);
		}
		return topicId;
	}
	
	public int likeOpinion(long id){
		return opinionDao.likeOpinion(id);
	}
	
	@Transactional(rollbackFor={Exception.class})
	public void incrNumOfConfused(int id,int confusedUserId) throws RecordNotFoundException{
		Topic topic = this.getTopicById(id);
		if(topic==null) throw new RecordNotFoundException(id);
		int rows = topicDao.addConfusedUser(topic.getId(), confusedUserId);
		if(rows>0){
			topicDao.incrNumOfConfused(id);
			notifyUserAgent.sendNotifyForTimelineEventDynamic(topic.getAuthor(), TimelineEvent.TYPE_TOPIC, topic.getId());
			mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(id));
		}
	}
	
	public int decrNumOfConfused(int topicId,int userId){
		int rows = topicDao.removeConfusedUser(topicId, userId);
		if(rows>0){
			topicDao.decrNumOfConfused(topicId);
			mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(topicId));
		}
		return rows;
	}
	
	public List<User> listTopicConfused(int topicId,int limit){
		return topicDao.listTopicConfused(topicId, limit);
	}
	
	public Topic getTopicById(int id){
		Topic topic = (Topic) mcs.get(MCKey.TOPIC_BY_ID, String.valueOf(id));
		if(topic==null){
			topic  = topicDao.getTopicById(id);
			if(topic!=null) mcs.set(MCKey.TOPIC_BY_ID, String.valueOf(id), MCKey.TOPIC_CACHE_TIMEOUT, topic);
		}
		return topic;
	}
	
	
	@Transactional(rollbackFor={Exception.class})
	public int settingOptions(List<TopicOptions> list){
		return optionsDao.settingOptions(list);
	}
	
	public TopicOpinion getTopicOpinionById(long id){
		return opinionDao.getTopicOpinionById(id);
	}
	
	@SuppressWarnings("unchecked")
	public List<TopicOptions> getOptions(int topicId){
		List<TopicOptions> list = (List<TopicOptions>) mcs.get(MCKey.TOPIC_OPTIONS, String.valueOf(topicId));
		if(list==null){
			list =  optionsDao.getOptions(topicId);
			if(list!=null) mcs.set(MCKey.TOPIC_OPTIONS, String.valueOf(topicId), MCKey.TOPIC_CACHE_TIMEOUT, list);
		}
		return list;
	}
	
	public Integer getChoicedOptionIdByUserIdAndTopicId(int userId, int topicId){
		return relatedTopidDao.getChoicedOptionIdByUserIdAndTopicId(userId, topicId);
	}
	
	
	private void addTopicIndex(int topicId){
		try {
			HttpUtil.request(ApplicationProperties.get("search.url")+"/topic/index.shtml?topicId="+topicId, "GET".intern(), "UTF-8".intern(), null);
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
		}
	}
	
	
	public void verify(int id,int pass,String reason) throws Exception{
		List<TopicOptions> options = null;
		if(pass==0){
			options = this.getOptions(id);
		}
		Topic topic = doVerify(id,pass);
		if(topic!=null){
			if(pass==1){
				addTopicIndex(id);
				notifyUserAgent.sendNotifyForTopicVerifyPassed(topic);
				notifyUserAgent.sendNotifyToFriendForHaveNewTopic(topic);
			}else{
				notifyUserAgent.sendNotifyForTopicVerifyReject(topic, options,reason);
			}
		}
	}
	
	@Transactional(rollbackFor={Exception.class})
	private Topic doVerify(int id,int pass) throws Exception{
		mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(id));
		int succ = topicDao.updateVisible(id, pass);
		if(succ>0){
			Topic topic = getTopicById(id);
			if(topic.getVerifyCount()==1){
				if(pass==1){
					//只有新话题才会触发
					userExtInfoService.incrNumOfRelatedTopic(topic.getAuthor());
				}else{
					deleteTopic(topic);
				}
				return topic;
			}
		}
		return null;
	}
	
	@Transactional(rollbackFor={Exception.class})
	private int deleteTopic(Topic topic) throws Exception{
		int succ = topicDao.deleteTopic(topic.getId());
		if(succ>0){
			succ = timelineService.deleteTopicEvent(topic.getAuthor(), topic.getTimelineId());
			if(succ==0) throw new Exception("deleteTopic["+topic.getId()+"] deleteTopicEvent fail.");
			mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(topic.getId()));
			mcs.delete(MCKey.TOPIC_OPTIONS, String.valueOf(topic.getId()));
			optionsDao.deleteOptionByTopicId(topic.getId());
		}
		return succ;
	}
	
	public int choiceOption(int userId,int topicId,int optionId)
			throws RecordNotFoundException{
		int succ =  choiceOption(userId,topicId,optionId,false);
		if(succ>0)
			userExtInfoService.incrNumOfRelatedTopic(userId);
		return succ;
	}
	
	@Transactional(rollbackFor={Exception.class})
	public int choiceOption(int userId,int topicId,int optionId,boolean isOwner) 
			throws RecordNotFoundException{
		Topic topic = getTopicById(topicId);
		if(topic==null) throw new RecordNotFoundException(topicId);
		RelatedTopic relate = new RelatedTopic();
		relate.setIsOwnTopic(topic.getAuthor()==userId?1:0);
		relate.setOptionId(optionId);
		if(isOwner) relate.setIsOwnTopic(1);
		relate.setTopicId(topicId);
		relate.setCreateAt(new Date());
		relate.setUserId(userId);
		int succ = relatedTopidDao.addRelatedTopic(relate);
		if(succ>0){
			incrTopicNumOfParticipate(topicId);
			incrOptionNumOfChoices(topicId,optionId);
			clearConsensusUserAmountCache(optionId);
		}
		return succ;
	}
	
	public boolean alreadyChoice(int userId,int topicId){
		return optionsDao.alreadyChoice(userId, topicId);
	}
	
	public int incrOptionNumOfChoices(int topicId,int optionId){
		int succ = optionsDao.incrOptionNumOfChoices(optionId);
		if(succ>0) mcs.delete(MCKey.TOPIC_OPTIONS, String.valueOf(topicId));
		return succ;
	}
	public int incrTopicNumOfParticipate(int id){
		int succ = topicDao.incrTopicNumOfParticipate(id);
		if(succ>0) mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(id));
		return succ;
	}
	
	public int increTopicNumOfOpinion(int id){
		int succ = topicDao.increTopicNumOfOpinion(id);
		if(succ>0) mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(id));
		return succ;
	}
	public long postTopicOpinion(TopicOpinion opinion){
		opinion.setIdx(Long.MAX_VALUE-System.currentTimeMillis());
		opinion.setCreateAt(new Date());
		opinionDao.postTopicOpinion(opinion);
		if(opinion.getId()!=0){
			Topic topic = getTopicById(opinion.getTopicId());
			notifyUserAgent.sendNotifyForNewTopicOpinion(opinion.getReviewer(), topic.getAuthor(), topic.getId(), opinion.getContent());
			increTopicNumOfOpinion(opinion.getTopicId());
		}
		return opinion.getId();
	}
	
	public int decrTopicNumOfOpinion(int id){
		int succ = topicDao.decrTopicNumOfOpinion(id);
		if(succ>0) mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(id));
		return succ;
	}
	
	public int deleteOpinion(int reviewer,int topicId,long id){
		int rows =   opinionDao.deleteOpinion(reviewer, id);
		if(rows>0) decrTopicNumOfOpinion(topicId);
		return rows;
	}
	
	public List<RelatedTopic> listRelatedTopic(int userId,int offset,int limit){
		return relatedTopidDao.listRelatedTopic(userId, offset, limit);
	}
	
	public List<TopicOpinion> listTopicOpinion(int topicId,long idx,int limit){
		return opinionDao.listTopicOpinion(topicId, idx, limit);
	}
	
	@Transactional(rollbackFor={Exception.class})
	public int incrTopicNumOfComment(int id){
		int succ = topicDao.incrTopicNumOfComment(id);
		if(succ>0) mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(id));
		return succ;
	}
	
	@Transactional(rollbackFor={Exception.class})
	public int decrTopicNumOfComment(int id){
		int succ = topicDao.decrTopicNumOfComment(id);
		if(succ>0) mcs.delete(MCKey.TOPIC_BY_ID, String.valueOf(id));
		return succ;
	}
	
	public List<ConsensusUser> listConsensusUser(Integer topicId,Integer gender,int selftUserId,int optionId,int offset,int limit){
		Map<String,Object> parameter = new HashMap<String,Object>();
		parameter.put("selftUserId", selftUserId);
		parameter.put("topicId", topicId);
		parameter.put("optionId", optionId);
		parameter.put("offset", offset);
		parameter.put("limit", limit);
		if(gender!=null)
			parameter.put("gender", gender);
		
		return optionsDao.listConsensusUser(parameter);
	}
	
	public int countConsensusUser(Integer topicId,Integer gender,int selftUserId,int optionId){
		String cacheKey = this.getTopicConsensusAmountKey(gender, optionId);
		Integer amount = (Integer)mcs.get(cacheKey, "");
		if(amount==null){
			Map<String,Object> parameter = new HashMap<String,Object>();
			parameter.put("selftUserId", selftUserId);
			parameter.put("optionId", optionId);
			parameter.put("topicId", topicId);
			if(gender!=null)
				parameter.put("gender", gender);
			amount =  optionsDao.countConsensusUser(parameter);
			if(amount==null) mcs.set(cacheKey, "", MCKey.TOPIC_CACHE_TIMEOUT, 0);
			else mcs.set(cacheKey, "", MCKey.TOPIC_CACHE_TIMEOUT,amount);
		}
		return amount;
	}
	
	private void clearConsensusUserAmountCache(int optionId){
		mcs.delete(getTopicConsensusAmountKey(null,optionId), "");
		mcs.delete(getTopicConsensusAmountKey(1,optionId), "");
		mcs.delete(getTopicConsensusAmountKey(2,optionId), "");
	}
	
	
	public SearchResult listTopicOfTop(String idxf,int mood,int offset,int limit) throws Exception{
		StringBuilder url = new StringBuilder();
		url.append(ApplicationProperties.get("search.url"))
		.append("/topic/search/top.shtml?idxf=").append(idxf)
		.append("&mood=").append(mood)
		.append("&offset=").append(offset)
		.append("&limit=").append(limit);
		return ServerHelper.searchForList(url.toString());
	}
	
	public SearchResult listTopicOfMoodMatch(String idxf,int mood,int offset,int limit) throws Exception{
		StringBuilder url = new StringBuilder();
		url.append(ApplicationProperties.get("search.url"))
		.append("/topic/search/mood_match.shtml?idxf=").append(idxf)
		.append("&mood=").append(mood)
		.append("&offset=").append(offset)
		.append("&limit=").append(limit);
		return ServerHelper.searchForList(url.toString());
	}
	
	public SearchResult listTopicOfNew(String idxf,int mood,int offset,int limit) throws Exception{
		StringBuilder url = new StringBuilder();
		url.append(ApplicationProperties.get("search.url"))
		.append("/topic/search/new.shtml?idxf=").append(idxf)
		.append("&mood=").append(mood)
		.append("&offset=").append(offset)
		.append("&limit=").append(limit);
		return ServerHelper.searchForList(url.toString());
	}
	
	public SearchResult listTopicOfActive(String idxf,int mood,int offset,int limit) throws Exception{
		StringBuilder url = new StringBuilder();
		url.append(ApplicationProperties.get("search.url"))
		.append("/topic/search/active.shtml?idxf=").append(idxf)
		.append("&mood=").append(mood)
		.append("&offset=").append(offset)
		.append("&limit=").append(limit);
		return ServerHelper.searchForList(url.toString());
	}
	
	public SearchResult listTopicOfHot(String idxf,int mood,int offset,int limit) throws Exception{
		StringBuilder url = new StringBuilder();
		url.append(ApplicationProperties.get("search.url"))
		.append("/topic/search/hot.shtml?idxf=").append(idxf)
		.append("&mood=").append(mood)
		.append("&offset=").append(offset)
		.append("&limit=").append(limit);
		return ServerHelper.searchForList(url.toString());
	}
	
	public SearchResult listTopicOfMatch(String idxf,int userId,int offset,int limit) throws Exception{
		StringBuilder url = new StringBuilder();
		url.append(ApplicationProperties.get("search.url"))
		.append("/topic/search/match.shtml?idxf=").append(idxf)
		.append("&userId=").append(userId)
		.append("&offset=").append(offset)
		.append("&limit=").append(limit);
		return ServerHelper.searchForList(url.toString());
	}
	
	public SearchResult searchByKeyword(String idxf,String keyword,int offset,int limit) throws Exception{
		StringBuilder url = new StringBuilder();
		url.append(ApplicationProperties.get("search.url"))
		.append("/topic/search.shtml?idxf=").append(idxf)
		.append("&keyword=").append(keyword)
		.append("&offset=").append(offset)
		.append("&limit=").append(limit);
		return  ServerHelper.searchForList(url.toString());
	}

	public void addTopicTagForUser(int userId,String tag){
		this.topicDao.addTopicTagForUser(userId, tag);
	}
	
	public String getTopicTagForUser(int userId){
		return topicDao.getTopicTagForUser(userId);
	}

	@Override
	public int getOwner(long eventId) throws RecordNotFoundException{
		TopicOpinion opinion = getTopicOpinionById(eventId);
		if(opinion == null) throw new RecordNotFoundException(eventId);
		return opinion.getReviewer();
	}

	@Override
	@Transactional(rollbackFor={Exception.class})
	public int increNumOfReview(long id) {
		int succ =  opinionDao.increNumOfReview(id);
		if(succ>0){
			TopicOpinion opinion = getTopicOpinionById(id);
			incrTopicNumOfComment(opinion.getTopicId());
		}
		return succ;
	}


	@Override
	@Transactional(rollbackFor={Exception.class})
	protected int decreNumOfReview(long id) {
		int succ =  opinionDao.decreNumOfReview(id);
		if(succ>0){
			TopicOpinion opinion = getTopicOpinionById(id);
			decrTopicNumOfComment(opinion.getTopicId());
		}
		return succ;
	}
	
	public List<TopicType> listTopicType(){
		return topicDao.listTopicType();
	}
	
	public List<Topic> fetchTopicByType(int type,int offset,int limit){
		return topicDao.fetchTopicByType(type, offset, limit);
	}
	
	public List<Integer> listTopicIdByType(int type,Date pointInTime,int offset,int limit){
		return topicDao.listTopicIdByType(type, pointInTime, offset,limit);
	}

}
